/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package papacharly.encheres.dao;

import java.lang.reflect.ParameterizedType;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.DataException;
import papacharly.encheres.log.Logger;

/**
 *
 * @author actimage
 */
public class GenericService<T> {

    /**
     * Logger
     */
    Logger log = Logger.getLogger(this.getClass().getSimpleName());
    /**
     * Permet de connaitre le type d'objet que l'on manipule
     */
    private Class<T> persistentClass;

    public GenericService() {
        persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    /**
     * Relève tous les objets de type T
     * @return la liste des objets de type T
     */
    public List<T> getAll() {
        log.info("(" + persistentClass.getName() + ") GetAll");
        /*
         * Utilise ConnectionFactory pour retrouver la session Hibernate.
         */
        Session session = ConnectionFactory.getSession();
        try {
            return session.createQuery("select t from " + persistentClass.getName() + " t").list();
        } catch (Exception e) {
            log.error("(" + persistentClass.getName() + ") GetAll - KO", e);
            return null;
        } finally {
            if (session != null) {
                try {
//                    session.close();
                } catch (HibernateException e) {
                    log.error("Hibernate Exception " + e.getMessage());
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * Enregistre l'objet de type T dans la base de données
     * @param objet à sauvegarder
     * @return true si le déroulement est ok, false sinon
     */
    public boolean create(T objet) throws DataException {
        log.info("(" + persistentClass.getName() + ") Create : " + ((objet == null) ? "null" : objet.toString()));
        /*
         * Utilise ConnectionFactory pour retrouver la session Hibernate.
         */
        Session session = ConnectionFactory.getSession();
        Transaction tc = null;
        try {
            /**
             * Sauvegarde l'objet
             */
            tc = session.beginTransaction();
            session.save(objet);
            session.flush();
            tc.commit();
            return true;

        } catch (DataException ex) {
            ex.printStackTrace();
            log.warn("(" + persistentClass.getName() + ") Create - Erreur de données (DataException)");
            if (tc != null) {
                tc.rollback();
            }
            throw ex;
        } catch (Exception ex) {
            ex.printStackTrace();
            if (tc != null) {
                tc.rollback();
            }
            declareUnhandledExceptionOnObject(objet);
            return false;
        } finally {
            if (session != null) {
                try {
//                    session.close();
                } catch (HibernateException e) {
                    log.error("Hibernate Exception " + e.getMessage());
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * Enregistre l'objet de type T dans la base de données sans COMMIT ou ROLLBACK
     * @param objet L'objet à sauvegarder.
     * @param session La session à utiliser pour réaliser l'ordre d'insertion.
     * @return <code>boolean</code> - <code>true</code> si tout s'est bien passé, <code>false</code> sinon.
     */
    public boolean createNoCommit(T objet, Session session) {
        log.info("createNoCommit - Objet : " + ((objet == null) ? "null" : objet.toString()));

        boolean erreur = false;

        try {
            session.save(objet);
            session.flush();
        } catch (DataException e) {
            log.warn("createNoCommit - Enregistrement KO", e);
            erreur = true;
            throw e;
        } catch (Exception e) {
            log.warn("createNoCommit - Enregistrement KO", e);
            erreur = true;
            declareUnhandledExceptionOnObject(objet);
        }

        return !erreur;
    }

    /**
     * Retrouve l'objet de type T identifié
     * @param id - identifiant qui permet de retrouver l'objet
     * @return l'objet lu, null si une erreur se produit
     */
    public T read(int id) {
        log.info("(" + persistentClass.getName() + ") Read (id=" + id + ")");

        // Vérification de l'existance
        if (!exists(id)) {
            log.warn("Read - Objet introuvable : " + persistentClass.getName() + "/id=" + id);
            return null;
        }

        /*
         * Utilise ConnectionFactory pour retrouver la session Hibernate.
         */
        Session session = ConnectionFactory.getSession();
        Transaction tc = null;
        T retValue = null;
        try {
            /**
             * Lecture de l'objet
             */
            //return (T) session.load(persistentClass, id);
            tc = session.beginTransaction();
            retValue = (T) session.load(persistentClass, id);
            if (retValue != null) {
                session.refresh(retValue);
            }
            session.flush();
            tc.commit();
            return retValue;
        } catch (ObjectNotFoundException ex) {
            log.warn("Read - Objet introuvable : " + persistentClass.getName() + "/id=" + id);
            return null;
        } catch (Exception ex) {
            log.error(ex.getMessage());
            if (tc != null) {
                tc.rollback();
            }
            declareUnhandledExceptionOnObject(retValue);
            return null;
        } finally {
            if (session != null) {
                try {
//                    session.close();
                } catch (HibernateException e) {
                    log.error("Hibernate Exception " + e.getMessage());
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * Met à jour l'objet de type T dans la base de données
     * @param objet à mettre à jour
     * @return true si le déroulement est ok, false sinon
     */
    public boolean update(T objet) throws DataException {
        log.info("(" + persistentClass.getName() + ") Update : " + ((objet == null) ? "null" : objet.toString()));
        /*
         * Utilise ConnectionFactory pour retrouver la session Hibernate.
         */
        Session session = ConnectionFactory.getSession();
        Transaction tc = null;
        try {
            /**
             * Met à jour l'objet
             */
            tc = session.beginTransaction();
            session.update(objet);
            //session.merge(objet);
            session.flush();
            tc.commit();
            return true;
        } catch (ConstraintViolationException ex) {
            if (tc != null) {
                tc.rollback();
                log.warn("(" + persistentClass.getName() + ") Update - Une erreur de contrainte est survenue, la transaction est annulée, les changements ne sont pas appliqués sur la base de données " + ((objet == null) ? "null" : objet.toString()));
            } else {
                log.error("(" + persistentClass.getName() + ") Update - Une erreur de contrainte est survenue, la transaction n'a pu être annulée : " + ((objet == null) ? "null" : objet.toString()) + " - - " + ex.getMessage());
            }
            return false;
        } catch (DataException ex) {
            if (tc != null) {
                tc.rollback();
                log.warn("(" + persistentClass.getName() + ") Update - la transaction est annulée, les changements ne sont pas appliqués sur la base de données (DataException) " + ((objet == null) ? "null" : objet.toString()));
            } else {
                log.error("(" + persistentClass.getName() + ") Update - la transaction n'a pu être annulée (DataException) : " + ((objet == null) ? "null" : objet.toString()) + " - - " + ex.getMessage());
            }
            throw ex;
        } catch (Exception ex) {
            if (tc != null) {
                tc.rollback();
                log.warn("(" + persistentClass.getName() + ") Update - la transaction est annulée, les changements ne sont pas appliqués sur la base de données " + ((objet == null) ? "null" : objet.toString()));
            } else {
                log.error("(" + persistentClass.getName() + ") Update - la transaction n'a pu être annulée : " + ((objet == null) ? "null" : objet.toString()) + " - - " + ex.getMessage());
            }
            ex.printStackTrace();
            session.evict(objet);
            declareUnhandledExceptionOnObject(objet);
            return false;
        } finally {
            if (session != null) {
                try {
//                    session.close();
                } catch (HibernateException e) {
                    log.error("Hibernate Exception " + e.getMessage());
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * Met à jour l'objet de type T dans la base de données SANS procéder au commit ou rollback
     * @param objet L'objet à mettre à jour.
     * @param session La session qui sera utilisée pour mettre l'objet à jour.
     * @return <code>boolean</code> - <code>True</code> si tout c'est bien passé, <code>false</code> sinon.
     */
    public boolean updateNoCommit(T objet, Session session) {
        log.info("updateNoCommit - Objet : " + ((objet == null) ? "null" : objet.toString()));

        boolean erreur = false;

        try {
            /*
             * Met à jour l'objet
             */
            if (!session.getTransaction().isActive()) {
                log.info("updateNoCommit - Transaction " + session.getTransaction().toString() + " inactive. Aucune action ne sera effectuée.");
            } else {
                session.update(objet);
                session.flush();
            }

        } catch (ConstraintViolationException e) {
            log.warn("updateNoCommit - Une erreur de contrainte est survenue - Objet : " + ((objet == null) ? "null" : objet.toString()), e);
            erreur = true;
        } catch (DataException e) {
            log.warn("updateNoCommit - DataException - Objet : " + ((objet == null) ? "null" : objet.toString()), e);
            erreur = true;
        } catch (Exception e) {
            log.warn("updateNoCommit - Exception - Objet : " + ((objet == null) ? "null" : objet.toString()), e);
            erreur = true;
            session.evict(objet);
            declareUnhandledExceptionOnObject(objet);
        }

        return !erreur;
    }

    /**
     * Retourne un objet Session.
     * @return Session - L'objet Session créé.
     */
    public Session getSession() {
        log.info("(" + persistentClass.getName() + ") getSession");

        return ConnectionFactory.getSession();
    }

    /**
     * Retourne un objet Transaction.
     * @param session La session pour laquelle une transaction sera commencée.
     * @return Transaction - L'objet transaction créé.
     * @throws HibernateException
     */
    public Transaction getTransaction(Session session) throws HibernateException {
        Transaction tc = null;
        tc = session.beginTransaction();
        log.info("(" + persistentClass.getName() + ") getTransaction + " + tc.toString());
        return tc;
    }

    /**
     * Tente de COMMIT la transaction afferente à la session ou un ROLLBACK si le COMMIT tombe en erreur.
     * @param session La session à laquelle correspond la transaction.
     * @param tc La transaction sur laquelle less ordes sont passés.
     * @return boolean - True si tout c'est bien passé, false sinon.
     * @throws RuntimeException
     */
    public boolean tryCommit(Session session, Transaction tc) throws RuntimeException {
        log.info("tryCommit");
        boolean result = true;

        try {
            if (!tc.isActive()) {
                log.info("tryCommit - Transaction " + tc.toString() + " inactive. Aucune action ne sera effectuée.");
            } else if (tc.wasCommitted()) {
                log.info("tryCommit - Transaction " + tc.toString() + ". La transaction a déjà été COMMMIT.");
            } else if (tc.wasRolledBack()) {
                log.info("tryCommit - Transaction " + tc.toString() + ". La transaction a déjà été ROLLBACK.");
            } else {
                session.flush();
                tc.commit();
            }
        } catch (Exception e) {
            if (tc != null) {
                tc.rollback();
                log.warn("tryCommit - la transaction est annulée, les changements ne sont pas appliqués sur la base de données.");
            } else {
                log.error("tryCommit - la transaction n'a pu être annulée. " + e.getMessage());
            }
            result = false;
        }
        return result;
    }

    /**
     * <p>
     * Tente de ROLLBACK la transaction en cours.
     * </p>
     * @param session La session à laquelle correspond la transaction.
     * @param tc La transaction sur laquelle appliqué le ROLLBACK.
     * @return <code>boolean</code> - <code>True</code> si tout c'est bien passé, <code>false</code> sinon.
     * @throws RuntimeException
     */
    public boolean rollback(Session session, Transaction tc) throws RuntimeException {
        log.info("rollback");
        boolean result = true;

        try {
            if (!tc.isActive()) {
                log.info("rollback - Transaction " + tc.toString() + " inactive. Aucune action ne sera effectuée.");
            } else if (tc.wasCommitted()) {
                log.info("rollback - Transaction " + tc.toString() + ". La transaction a déjà été COMMMIT.");
            } else if (tc.wasRolledBack()) {
                log.info("rollback - Transaction " + tc.toString() + ". La transaction a déjà été ROLLBACK.");
            } else {
                tc.rollback();
                log.info("rollback - Transaction " + tc.toString() + " - ROLLBACK OK");
            }
            session.flush();
        } catch (Exception e) {
            log.error("rollback - ROLLBACK KO", e);
            result = false;
        }
        return result;
    }

    /**
     * Supprime l'objet de type T dans la base de données
     * @param objet à supprimer
     * @return true si le déroulement est ok, false sinon
     */
    public boolean delete(T objet) {
        log.info("(" + persistentClass.getName() + ") Delete : " + ((objet == null) ? "null" : objet.toString()));
        /*
         * Utilise ConnectionFactory pour retrouver la session Hibernate.
         */
        Session session = ConnectionFactory.getSession();
        Transaction tc = null;
        try {
            /**
             * Supprime l'objet
             */
            tc = session.beginTransaction();
            session.delete(objet);
            session.flush();
            tc.commit();
            return true;
        } catch (ConstraintViolationException ex) {
            if (tc != null) {
                tc.rollback();
                log.warn("(" + persistentClass.getName() + ") Delete - Une erreur de contrainte est survenue, la transaction est annulée, les changements ne sont pas appliqués sur la base de données " + ((objet == null) ? "null" : objet.toString()));
            } else {
                log.error("(" + persistentClass.getName() + ") Delete - Une erreur de contrainte est survenue, la transaction n'a pu être annulée : " + ((objet == null) ? "null" : objet.toString()) + " - " + ex.getMessage());
            }
            return false;
        } catch (Exception ex) {
            if (tc != null) {
                tc.rollback();
                log.warn("(" + persistentClass.getName() + ") Delete - la transaction est annulée, les changements ne sont pas appliqués sur la base de données " + ((objet == null) ? "null" : objet.toString()));
            } else {
                log.error("(" + persistentClass.getName() + ") Delete - la transaction n'a pu être annulée : " + ((objet == null) ? "null" : objet.toString()) + " - " + ex.getMessage());
            }
            ex.printStackTrace();
            declareUnhandledExceptionOnObject(objet);
            return false;
        } finally {
            if (session != null) {
                try {
//                    session.close();
                } catch (HibernateException e) {
                    log.error("Hibernate Exception " + e.getMessage());
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * Supprime l'objet identifié par son id de la base de données
     * @param id de l'objet à supprimer
     * @return true si le déroulement est ok, false sinon
     */
    public boolean deleteFromId(int id) throws Exception {
        /*
         * Utilise ConnectionFactory pour retrouver la session Hibernate.
         */
        Session session = ConnectionFactory.getSession();
        Transaction tc = null;
        T toDelete = null;
        try {
            /**
             * Supprime l'objet
             */
            toDelete = read(id);
            tc = session.beginTransaction();
            session.delete(toDelete);
            session.flush();
            tc.commit();
            return true;
        } catch (ConstraintViolationException ex) {
            if (tc != null) {
                tc.rollback();
                log.warn("(" + persistentClass.getName() + ") deleteFromId(" + id + ") - Une erreur de contrainte est survenue, la transaction est annulée, les changements ne sont pas appliqués sur la base de données");
            } else {
                log.error("(" + persistentClass.getName() + ") deleteFromId(" + id + ") - Une erreur de contrainte est survenue, la transaction n'a pu être annulée - " + ex.getMessage());
            }
            declareUnhandledExceptionOnObject(toDelete);
            throw ex;
        } catch (Exception ex) {
            if (tc != null) {
                tc.rollback();
                log.warn("(" + persistentClass.getName() + ") deleteFromId(" + id + ") - la transaction est annulée, les changements ne sont pas appliqués sur la base de données");
            } else {
                log.error("(" + persistentClass.getName() + ") deleteFromId(" + id + ") - la transaction n'a pu être annulée - " + ex.getMessage());
            }
            ex.printStackTrace();
            declareUnhandledExceptionOnObject(toDelete);
            return false;
        } finally {
            if (session != null) {
                try {
//                    session.close();
                } catch (HibernateException e) {
                    log.error("Hibernate Exception " + e.getMessage());
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * Vérifie si un objet avec l'identifiant passé en paramètre existe dans la
     * base de données.
     * @param id l'identifiant de l'objet à rechercher dans la base de données
     * @return true si l'objet a été trouvé, false sinon
     */
    public boolean exists(int id) {
        log.info("Exists : id=" + id);
        /*
         * Utilise ConnectionFactory pour retrouver la session Hibernate.
         */
        Session session = ConnectionFactory.getSession();
        try {
            Transaction tc = session.beginTransaction();
            List<T> list = session.createQuery("select t from " + persistentClass.getName() + " t where t.id = :id").setParameter("id", id).list();
            tc.commit();
            if (list.size() > 0) {
                return true;
            }
            return false;
        } catch (Exception ex) {
            log.warn("(" + persistentClass.getName() + ") exists(" + id + ") - " + ex.getMessage());
            return false;
        } finally {
            if (session != null) {
                try {
//                    session.close();
                } catch (HibernateException e) {
                    log.error("Hibernate Exception " + e.getMessage());
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * Attention, cette méthode n'est à utiliser que dans des conditions d'erreur IRRECUPERABLES.
     * Les objets en cours d'utilisation risquent de ne plus accéder à leur propriétés.
     * Nettoye la session.
     */
    public void declareUnhandledExceptionOnObject(Object obj) {
        log.error("Unhandled Error On Object declared");
        try {
            ConnectionFactory.getSession().evict(obj);
        } catch (Exception ex) {
            log.error("Unhandled Error On Object FAILED !!! - " + ex.getMessage());
        }
    }

    /**
     * Détache l'objet de la session de persistance hibernate
     * @param obj
     */
    public void evict(T obj) {
        ConnectionFactory.getSession().evict(obj);
    }
}
